﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Engine.Input;

namespace Engine.Cameras
{
    public abstract class Camera : Entity
    {
        #region Camera Manager
        public static Camera ActiveCamera;
        #endregion

        #region World View Projection

        protected Matrix view;
        public Matrix View { get { return view; } }

        protected Matrix projection;
        public Matrix Projection { get { return projection; } }

        protected Matrix viewProjection;
        public Matrix ViewProjection { get { return viewProjection; } }

        #endregion

        #region Position / Aim / Near Far Clips
        public override Vector3 Position
        {
            get { return base.Position; }
            set 
            {
                base.Position = value;
                UpdateMatrices();
            }
        }

        private Vector3 target;
        public Vector3 Target
        {
            get { return target; }
            set
            {
                target = value;
                view = Matrix.CreateLookAt(Position, target, Vector3.Up);
            }
        }

        public Vector3 Aim
        {
            get { return view.Forward; }
            //set
            //{

            //}
        }

        protected float nearClip = 0.1f;
        public float NearClip { get { return nearClip; } set { nearClip = value; } }

        protected float farClip = 100.0f;
        public float FarClip { get { return farClip; } set { farClip = value; } }

        protected float aspectRatio;
        public float AspectRatio { get { return projection.M11 / projection.M22; } }

        #endregion

        public Camera()
            : base(null)
        {

        }

        public override void Update(GameTime gameTime)
        {
            base.Update(gameTime);
        }

        public virtual void UpdateMatrices()
        {
            //Calculate the view matrix according to the current world.
            Matrix.Invert(ref localWorld, out view); //The view is the inverted world.

            //Calculate the viewProjection Matrix.
            Matrix.Multiply(ref view, ref projection, out viewProjection);
        }

    }

    public class PerspectiveCamera : Camera
    {
        protected float fov = MathHelper.PiOver4;
        public float FOV 
        { 
            get { return fov; }
            set
            {
                projection = Matrix.CreatePerspectiveFieldOfView(value, aspectRatio, nearClip, farClip);
                fov = value;
                UpdateMatrices();
            }
        }

        public PerspectiveCamera(float fov, float aspectRatio, float nearClip, float farClip)
            : base()
        {
            projection = Matrix.CreatePerspectiveFieldOfView(fov, aspectRatio, nearClip, farClip);
        }

        public override void Update(GameTime gameTime)
        {
            //throw new NotImplementedException();
        }
    }

    public class ArcCamera : PerspectiveCamera, IInputUpdate
    {
        public float CameraDefaultArc { get; private set; }
        public float CameraDefaultRotation { get; private set; }
        public float CameraDefaultDistance { get; private set; }
        float cameraArc;
        float cameraRotation;
        float cameraDistance;


        float cameraRotateSpeed = .1f;
        float cameraZoomSpeed = .01f;
        float cameraMaxDistance = 10.0f;
        float cameraMinDistance = 1.2f;

        public ArcCamera(
            float fov, 
            float aspectRatio, 
            float nearClip, 
            float farClip,
            float defaultArc, 
            float defaultRotation, 
            float defaultDistance)
            : base(fov, aspectRatio, nearClip, farClip)
        {
            cameraArc = CameraDefaultArc = defaultArc;
            cameraRotation = CameraDefaultRotation = defaultRotation;
            cameraDistance = CameraDefaultDistance = defaultDistance;
        }

        public ArcCamera(
            float fov,
            float aspectRatio,
            float nearClip,
            float farClip,
            float defaultArc,
            float defaultRotation,
            float defaultDistance,
            float rotateSpeed,
            float zoomSpeed,
            float maxDistance,
            float minDistance)
            : this(fov, aspectRatio, nearClip, farClip, defaultArc, defaultRotation, defaultDistance)
        {
            cameraRotateSpeed = rotateSpeed;
            cameraZoomSpeed = zoomSpeed;
            cameraMaxDistance = maxDistance;
            cameraMinDistance = minDistance;
        }

        public override void Update(GameTime gameTime)
        {
            if (inputState == null)
                return;

            float time = (float)gameTime.ElapsedGameTime.TotalMilliseconds;

            KeyboardState currentKeyboardState = inputState.CurrentKeyboardStates[0];
            GamePadState currentGamePadState = inputState.CurrentGamePadStates[0];

            // should we reset the camera?
            if (currentKeyboardState.IsKeyDown(Keys.R) ||
                currentGamePadState.Buttons.RightStick == ButtonState.Pressed)
            {
                cameraArc = CameraDefaultArc;
                cameraDistance = CameraDefaultDistance;
                cameraRotation = CameraDefaultRotation;
            }

            // Check for input to rotate the camera up and down around the model.
            if (currentKeyboardState.IsKeyDown(Keys.Up) ||
                currentKeyboardState.IsKeyDown(Keys.W))
            {
                cameraArc += time * cameraRotateSpeed;
            }

            if (currentKeyboardState.IsKeyDown(Keys.Down) ||
                currentKeyboardState.IsKeyDown(Keys.S))
            {
                cameraArc -= time * cameraRotateSpeed;
            }

            cameraArc += currentGamePadState.ThumbSticks.Right.Y * time *
                cameraRotateSpeed;

            // Limit the arc movement.
            cameraArc = MathHelper.Clamp(cameraArc, -90.0f, 90.0f);

            // Check for input to rotate the camera around the model.
            if (currentKeyboardState.IsKeyDown(Keys.Right) ||
                currentKeyboardState.IsKeyDown(Keys.D))
            {
                cameraRotation += time * cameraRotateSpeed;
            }

            if (currentKeyboardState.IsKeyDown(Keys.Left) ||
                currentKeyboardState.IsKeyDown(Keys.A))
            {
                cameraRotation -= time * cameraRotateSpeed;
            }

            cameraRotation += currentGamePadState.ThumbSticks.Right.X * time *
                cameraRotateSpeed;

            // Check for input to zoom camera in and out.
            if (currentKeyboardState.IsKeyDown(Keys.Z))
                cameraDistance += time * cameraZoomSpeed;

            if (currentKeyboardState.IsKeyDown(Keys.X))
                cameraDistance -= time * cameraZoomSpeed;

            cameraDistance += currentGamePadState.Triggers.Left * time
                * cameraZoomSpeed;
            cameraDistance -= currentGamePadState.Triggers.Right * time
                * cameraZoomSpeed;

            // clamp the camera distance so it doesn't get too close or too far away.
            cameraDistance = MathHelper.Clamp(cameraDistance,
                cameraMinDistance, cameraMaxDistance);

            Matrix unrotatedView = Matrix.CreateLookAt(
                new Vector3(0, 0, -cameraDistance), Vector3.Zero, Vector3.Up);

            view= Matrix.CreateRotationY(MathHelper.ToRadians(cameraRotation)) *
                          Matrix.CreateRotationX(MathHelper.ToRadians(cameraArc)) *
                          unrotatedView;
        }

        private InputState inputState;
        public void UpdateInputState(InputState input)
        {
            this.inputState = input;
        }
    }

    public class TargetCam : PerspectiveCamera, IInputUpdate
    {
        public TargetCam(float fov, float aspectRatio, float nearClip, float farClip)
            : base(fov, aspectRatio, nearClip, farClip)
        {

        }

        public override void Update(GameTime gameTime)
        {
            if (inputState == null)
                return;

            KeyboardState ks = inputState.CurrentKeyboardStates[0];

            Vector3 targetDelta = Vector3.Zero;
            float moveSpeed = 10.0f;
            float dt = (float)gameTime.ElapsedGameTime.TotalSeconds;

            if (ks.IsKeyDown(Keys.A))
	        {
                targetDelta.X -= moveSpeed * dt;
	        }

            if (ks.IsKeyDown(Keys.D))
            {
                targetDelta.X += moveSpeed * dt;
            }

            if (ks.IsKeyDown(Keys.W))
            {
                targetDelta.Z += moveSpeed * dt;
            }

            if (ks.IsKeyDown(Keys.S))
            {
                targetDelta.Z -= moveSpeed * dt;
            }

            Target = Target + targetDelta;

            base.Update(gameTime);
        }

        private InputState inputState;
        public void UpdateInputState(InputState input)
        {
            this.inputState = input;
        }
    }

    public interface IInputUpdate
    {
        void UpdateInputState(InputState input);
    }
    //public class ChaseCamera : Camera
    //{

    //}
}
